#include "JunctionFinder.h"
#include "RunFinder.h"
#include "NVBRWalker.h"
#include "Debug.h"

void JunctionFinder::GetJunctions(const std::vector < ViewState > *pVecStates1, 
                                  const std::vector < ViewState > *pVecStates2, 
                                  const Criteria *pCriteria, Info *pInfo)
{
	S32 nState = 0;
	std::vector < ViewState >::const_iterator iter;
	
	if (!pVecStates1->size())
		return;	
	// in this function we simulate the tracing of the path all over again
	// the first point is the point we started from before doing any WalkSingle()s
	// afterwards, we continually search for a junction where we are and then go again
	// it should be noted that this way we miss the last point in pVecStates1,
	// which is supposed to happen since the last point in there
	// is the point where we stopped doing processing in the WalkUntil() routine originally
	iter = pVecStates1->begin();
	View::SetViewState(&*iter);
	for(++iter; iter != pVecStates1->end(); ++iter) {
		Found f = AddIfJunctionPoint(pInfo, pCriteria);
		if (f.IsFoundAnything()) 
			UpdateStateIndex(pInfo, f, nState);
		View::SetViewState(&*iter);
		++nState;
	}
	// same trick as above done for the second state sequence
	// but here, the first state of pVecStates2 is the same first of pVecStates1,
	// therefore, we do not use it
	
	if (!pVecStates2->size())
		abort(); // there should be at least a repetition on the same first state from pVecStates1
	
	iter = pVecStates2->begin();
	++iter;
	if (iter == pVecStates2->end())
		return;
	nState = pVecStates1->size() + 1;
	View::SetViewState(&*iter);
	for(++iter; iter != pVecStates2->end(); ++iter) {
		SetCenterAngle(GetCenterAngle() + 180.0); // we flip since we are now going on the 
		                                          // opposite direction to the one above
		Found f = AddIfJunctionPoint(pInfo, pCriteria);
		if (f.IsFoundAnything()) 
			UpdateStateIndex(pInfo, f, nState);
		View::SetViewState(&*iter);
		++nState;
	}
}

void JunctionFinder::GetJunctionInSingleDirection(const ViewState *pState, 
                                                  const Criteria *pJc,
                                                  std::vector < Info::Node > *pJunctionVec,
                                                  std::vector < Info::Node90 > *p90JunctionVec)
{
	View::SetViewState(pState);
	AddIfJunctionPointInSingleDirection(pJunctionVec, p90JunctionVec, pJc);
}

JunctionFinder::Found JunctionFinder::AddIfJunctionPoint(Info *pInfo, const Criteria *pCriteria)
{
	Found f, f2;
	f = AddIfJunctionPointInSingleDirection(&pInfo->m_vecJunction,
	                                        &pInfo->m_vecJunction90,
	                                        pCriteria);
	SetCenterAngle(GetCenterAngle() + 180.0);
	f2 = AddIfJunctionPointInSingleDirection(&pInfo->m_vecJunctionOppositeAngle,
	                                         &pInfo->m_vecJunction90OppositeAngle,
	                                         pCriteria);
	SetCenterAngle(GetCenterAngle() - 180.0);
	if (f2.IsFound()) f.SetFoundOpposite();
	if (f2.IsFound90()) f.SetFound90Opposite();
	return f;
}

JunctionFinder::Found JunctionFinder::AddIfJunctionPointInSingleDirection(
              std::vector< Info::Node > *pJunctionVec,
              std::vector< Info::Node90 > *p90JunctionVec, const Criteria *pCriteria)
{
	RunFinder<JunctionFinder> rf(this);
	Run r;
	Found f;
	std::vector<Run> runsVector;
	
	r = rf.GetVertRunForPoint(0, 0, 0xFF);
	rf.GetAdjacentVertRuns(r, 1, 0xFF, &runsVector);
	if (!runsVector.size()) return f;
	std::vector<bool> vecBlockedFromAbove(runsVector.size(), false);
	std::vector<bool> vecBlockedFromBelow(runsVector.size(), false);
	Info::Node90 *pNode90 = NULL; // node in which we store info on a 90 junction we found
	Info::Node *pNode = NULL; // node in which we store info on a normal junction we found	
	for(U32 j = 1; j < runsVector.size(); ++j) {
		for(U32 i = 0; i < runsVector.size() - j; ++i) {
			U32 k;
			for(k = i; k < (i + j ) && !vecBlockedFromBelow[k] && 
			                           !vecBlockedFromAbove[k + 1]; ++k);
			if (k < (i + j))   // we must not have our junction's runs
				continue;  // intersect with existing junctions' runs
			
			if (pCriteria->m_nMaxJunctionDistance > 0 &&
			    runsVector[i + j].m_nStartPos - runsVector[i].EndPos() > 
			    pCriteria->m_nMaxJunctionDistance) {
				F64 nRow, nCol = 1;
				/* we find out which one of these runs is more far from the run "r" */
				if (abs(r.AvgPos() - runsVector[i + j].AvgPos()) < 
				    abs(r.AvgPos() - runsVector[i].AvgPos()))
					nRow = runsVector[i + j].m_nStartPos - 1;
				else
					nRow = runsVector[i].EndPos() + 1;
				TransformPoints(&nRow, &nCol);
				if (!pNode90) {
					p90JunctionVec->push_back(Info::Node90());
					pNode90 = &(*p90JunctionVec)[p90JunctionVec->size() - 1];
					pNode90->m_nViewStateIndex = -1;
					f.SetFound90();
				}
				pNode90->m_vecPtDraw.push_back(Point<S32>((S32)nRow, (S32)nCol));
				continue;
			}
			F64 nRow = (runsVector[i].m_nStartPos + runsVector[i].m_nLength - 1 + 
					runsVector[i + j].m_nStartPos) / 2;
			F64 nCol = 1;
			TransformPoints(&nRow, &nCol);
			NVBRWalker nw(GetSourceImage(), GetCenterRow(), GetCenterCol(), 
					GetCenterAngle(), runsVector[i], runsVector[i + j]);
			NVBRWalker::Info infoNVBR;
			if (nw.CanWalk(pCriteria->m_nMinSize, pCriteria->m_bPathAllowedToEnd, &infoNVBR)) {
//				std::cout << "Junction point: " << (S32)nRow << ", " << (S32)nCol << 
//						"\t(View Center: " << GetCenterRow() << ", " << 
//						GetCenterCol() << ")" << std::endl;
				if (!pNode) {
					pJunctionVec->push_back(Info::Node());
					pNode = &(*pJunctionVec)[pJunctionVec->size() - 1];
					pNode->m_nViewStateIndex = -1;
					f.SetFound();
					pNode->m_vecRunsEdge.resize(runsVector.size());
					pNode->m_vecEdgeCol.resize(runsVector.size());
				}
				pNode->m_vecPtDraw.push_back(Point<S32>((S32)nRow, (S32)nCol));
				vecBlockedFromAbove[i + j] = vecBlockedFromBelow[i] = true;
				pNode->m_vecRunsEdge[i] = infoNVBR.m_r1;
				pNode->m_vecRunsEdge[i + j] = infoNVBR.m_r2;
				pNode->m_vecEdgeCol[i] = pNode->m_vecEdgeCol[i + j] = infoNVBR.m_nCol;
			}
		}
	}
	if (pNode) 
		for(U32 i = 0; i < runsVector.size(); ++i) {
			Info::Node::Blocked b;
			if (vecBlockedFromAbove[i]) b.SetFromAbove();
			if (vecBlockedFromBelow[i]) b.SetFromBelow();
			pNode->m_vecBlocked.push_back(b);
		}
	return f;
}

void JunctionFinder::UpdateStateIndex(Info *pInfo, Found f, S32 nState)
{
#if defined(DEBUG_BREAK_AT_JUNCTION_AT_STATE) && DEBUG_BREAK_AT_JUNCTION_AT_STATE >= 0
	if (nState == DEBUG_BREAK_AT_JUNCTION_AT_STATE)
		Debug::DumpDebugInfoAndBreak(this);
#endif
	if (f.IsFound()) 
		pInfo->m_vecJunction[pInfo->m_vecJunction.size() - 1].m_nViewStateIndex = nState;
	if (f.IsFound90())
		pInfo->m_vecJunction90[pInfo->m_vecJunction90.size() - 1].m_nViewStateIndex = nState;
	if (f.IsFoundOpposite())
		pInfo->m_vecJunctionOppositeAngle[pInfo->m_vecJunctionOppositeAngle.size() - 1].m_nViewStateIndex = nState;
	if (f.IsFound90Opposite())
		pInfo->m_vecJunction90OppositeAngle[pInfo->m_vecJunction90OppositeAngle.size() - 1].m_nViewStateIndex = nState;
	
}
